<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"chuangges.gitee.io","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"right","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":"mac"},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="技术方案、项目难点、主流平台">
<meta property="og:type" content="article">
<meta property="og:title" content="低代码平台开发">
<meta property="og:url" content="https://chuangges.gitee.io/2023/08/14/22-%E4%BD%8E%E4%BB%A3%E7%A0%81%E5%B9%B3%E5%8F%B0/index.html">
<meta property="og:site_name" content="闯哥要去流浪了">
<meta property="og:description" content="技术方案、项目难点、主流平台">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/lowcode.png">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/lowcodefront.png">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/comfn.png">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/com_config.png">
<meta property="og:image" content="https://chuangges.gitee.io/images/web/formData.png">
<meta property="article:published_time" content="2023-08-14T06:26:58.000Z">
<meta property="article:modified_time" content="2023-09-20T03:05:12.367Z">
<meta property="article:author" content="chuangges">
<meta property="article:tag" content="前端技术栈">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://chuangges.gitee.io/images/web/lowcode.png">

<link rel="canonical" href="https://chuangges.gitee.io/2023/08/14/22-%E4%BD%8E%E4%BB%A3%E7%A0%81%E5%B9%B3%E5%8F%B0/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>低代码平台开发 | 闯哥要去流浪了</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="闯哥要去流浪了" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">闯哥要去流浪了</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">愿你岁月可回首，且以深情共白头</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://chuangges.gitee.io/2023/08/14/22-%E4%BD%8E%E4%BB%A3%E7%A0%81%E5%B9%B3%E5%8F%B0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/chuang.png">
      <meta itemprop="name" content="chuangges">
      <meta itemprop="description" content="chuangge's blog.">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="闯哥要去流浪了">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          低代码平台开发
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2023-08-14 14:26:58" itemprop="dateCreated datePublished" datetime="2023-08-14T14:26:58+08:00">2023-08-14</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2023-09-20 11:05:12" itemprop="dateModified" datetime="2023-09-20T11:05:12+08:00">2023-09-20</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%E6%A0%88/" itemprop="url" rel="index"><span itemprop="name">前端技术栈</span></a>
                </span>
            </span>

          <br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="far fa-file-word"></i>
              </span>
                <span class="post-meta-item-text">本文字数：</span>
              <span>12k</span>
            </span>
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="far fa-clock"></i>
              </span>
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              <span>11 分钟</span>
            </span>
            <div class="post-description">技术方案、项目难点、主流平台</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h1 id="一、技术方案"><a href="#一、技术方案" class="headerlink" title="一、技术方案"></a>一、技术方案</h1><blockquote>
<p>通过基于 React 的网页编辑器框架 craftjs 实现可编辑组件和用户界面等底层功能，然后封装组件布局及拖拽、组件事件配置、多页面路由系统等通用功能来实现多页面搭建能力。</p>
</blockquote>
  <div align="center">
      <img src="" data-original="/images/web/lowcode.png" alt="相关配置" width="100%">
  </div>

<h2 id="整体架构"><a href="#整体架构" class="headerlink" title="整体架构"></a>整体架构</h2><blockquote>
<p>一个低代码平台的前端核心一般组成为：组件面板、画布、配置面板。搭建过程一般是从组件面板中拖拽组件到画布，然后选中该组件使用配置面板对其样式和事件等进行配置，直到生成一个不同组件拼接成的完整页面，同时还需要有模拟浏览器环境的预览功能等。</p>
</blockquote>
  <div align="center">
      <img src="" data-original="/images/web/lowcodefront.png" alt="相关配置" width="100%">
  </div>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 组件树：key 为组件名，属性 parent child 描述组件关系的扁平树形结构，这样</span></span><br><span class="line"><span class="comment"> * 比多层嵌套结构便于操作。用户访问页面时首先加载所有 js 资源，render.js 加载完成后从 </span></span><br><span class="line"><span class="comment"> * id=&#x27;content&#x27; 的 script 标签获取组件树，然后 node bff 中间渲染层遍历 json 并从 </span></span><br><span class="line"><span class="comment"> * root 组件开始通过 React.createElement 等将所有组件拼装在一起组成完整页面。</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 组件的构建和发布：低代码平台发布时本质上发布两套资源并对应两套独立 webpack</span></span><br><span class="line"><span class="comment"> * 构建脚本，一套是低代码搭建平台的前端项目，发布时项目会被打包部署到服务器并为搭建平台</span></span><br><span class="line"><span class="comment"> * 的前端提供服务，另外一套脚本则同时将搭建平台中使用的组件资源打包构建为单独的组件资源</span></span><br><span class="line"><span class="comment"> * 并发布到 cdn，最终会得到类似的组件 cdn 资源地址：https://xx-cdn/render.js。</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line">&lt;!<span class="variable constant_">DOCTYPE</span> html&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">title</span>&gt;</span>低代码页面<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">script</span> <span class="attr">id</span>=<span class="string">&quot;content&quot;</span> <span class="attr">type</span>=<span class="string">&quot;application/json&quot;</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="comment">// 示例组件树：根组件、导航栏、背景、组件导航</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">const</span> demoTree = &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">root</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">child</span>: [nav, background],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">parent</span>: <span class="literal">null</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">nav</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">child</span>: [],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">parent</span>: root</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">link</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">child</span>: []</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">parent</span>: background</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">background</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">child</span>: [link],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">parent</span>: root</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;root&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://xx-cdn/root.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://xx-cdn/link.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://xx-cdn/background.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://xx-cdn/nav.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://xx-cdn/render.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span></span><br></pre></td></tr></table></figure>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 组件资源的构建：webpack libraryTarget: &#x27;window&#x27; 时构建后的资源加载后</span></span><br><span class="line"><span class="comment"> * 会自动挂载到全局 window，我们可以通过 &#x27;window.组件名&#x27; 的方式来获取相应的构建资源。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 除了所有的组件资源需要被挂载在 window，低代码组件的构建脚本和其他常规脚本</span></span><br><span class="line"><span class="comment"> * 还有两点差异：一、每个组件需要被单独打包构建，即每个组件需要作为单独的一个入口文件。</span></span><br><span class="line"><span class="comment"> * 二、对所有组件资源打包构建时需要配置 optimization 来将所有组件中用到的公共资源等</span></span><br><span class="line"><span class="comment"> * 抽离到单独文件 vender、common、manifest 来减少每个组件的体积，同时在 html 中也</span></span><br><span class="line"><span class="comment"> * 需要引入相应的公共资源，所以一个低代码构建的 html 页面应该如下：</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;dist&quot;</span>,</span><br><span class="line">    <span class="attr">library</span>: <span class="string">&quot;[name]&quot;</span>,</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;[name].js&quot;</span>,</span><br><span class="line">    <span class="attr">libraryTarget</span>: <span class="string">&quot;window&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;script src=<span class="string">&quot;https://cdn/vendor.js&quot;</span>&gt;&lt;/script&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn/common.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn/manifest.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn/root.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn/link.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn/background.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn/nav.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure>


<h2 id="组件布局"><a href="#组件布局" class="headerlink" title="组件布局"></a>组件布局</h2>  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 1、自由布局：根据鼠标拖动位置来放置组件，适用于高度还原设计图的场景。</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 拖拽组件改变位置：设置一个组件容器为相对定位并设置其子组件为绝对定位，</span></span><br><span class="line"><span class="comment"> * 同时监听子组件 onMouseDown 事件获取鼠标位置后设置子组件的位置属性 top left。</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 拖拽组件边缘改变尺寸：可基于 re-resizable 封装实现，监听节点边缘被</span></span><br><span class="line"><span class="comment"> * 拖拽时需要动态更新组件的 width height。注意监听组件位置的 onMouseMove 是</span></span><br><span class="line"><span class="comment"> * 监听拖拽 onDragstart 的子事件，所以要阻止事件冒泡和 onMouseMove 默认事件。</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 2、自动布局：根据 html 元素特点正常布局的搭建场景，主要提供多种模版。</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line">&lt;<span class="title class_">Resizable</span></span><br><span class="line">    size=&#123;&#123; <span class="attr">width</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">width</span>, <span class="attr">height</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">height</span> &#125;&#125;</span><br><span class="line">    onResizeStop=&#123;<span class="function">(<span class="params">e, direction, ref, d</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">            <span class="attr">width</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">width</span> + d.<span class="property">width</span>,</span><br><span class="line">            <span class="attr">height</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">height</span> + d.<span class="property">height</span>,</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;&#125;&gt;</span><br><span class="line">    <span class="title class_">Sample</span> <span class="keyword">with</span> size</span><br><span class="line">&lt;/<span class="title class_">Resizable</span>&gt;</span><br></pre></td></tr></table></figure>

<h2 id="组件事件"><a href="#组件事件" class="headerlink" title="组件事件"></a>组件事件</h2><blockquote>
<p>支持按钮可配置点击后跳转指定地址、打开弹窗、调用接口等。该功能需要有两个前提：每个组件都支持事件配置和注册全局事件的<code>通用性</code>、一个组件可以配置多个不同事件的<code>可扩展性</code>。难点是实现每个组件都可以调用接口和打开弹窗等功能，具体实现可在组件渲染时将事件方法作为组件的 props 属性传入，下面以点击 Link 组件时跳转到百度为例：</p>
</blockquote>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初级实现：tree 保存的方法无法被序列化，不可用。</span></span><br><span class="line"><span class="keyword">const</span> tree = &#123;</span><br><span class="line">    <span class="attr">root</span>: &#123;</span><br><span class="line">        <span class="attr">type</span>: <span class="title class_">Root</span>,</span><br><span class="line">        <span class="attr">child</span>: [link],</span><br><span class="line">        <span class="attr">parent</span>: <span class="literal">null</span> </span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">link</span>: &#123;</span><br><span class="line">        <span class="attr">type</span>: <span class="title class_">Link</span>,</span><br><span class="line">        <span class="attr">child</span>: []</span><br><span class="line">        <span class="attr">parent</span>: background,</span><br><span class="line">        <span class="attr">props</span>: &#123;</span><br><span class="line">            <span class="attr">onClick</span>:<span class="function">()=&gt;</span>&#123; <span class="variable language_">window</span>.<span class="property">href</span> = <span class="string">&quot;http://www.baidu.com&quot;</span> &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Link</span>: <span class="title class_">CraftComponent</span> = <span class="function">(<span class="params">props</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; onClick &#125; = props;</span><br><span class="line">    <span class="comment">// 声明组件需要支持 onClick，配置面板会为该组件提供事件配置入口</span></span><br><span class="line">    <span class="title function_">useRegister</span>(&#123; <span class="attr">events</span>: [<span class="string">&quot;onClick&quot;</span>] &#125;, []);</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> <span class="attr">onClick</span>=<span class="string">&#123;debounce(()</span> =&gt;</span> onClick &amp;&amp; onClick(), 300)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">            Link组件</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> 事件可配置化方案</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 实现原理：将事件拆分为分别存储于 store、tree 的行为方法、事件 schema。</span></span><br><span class="line"><span class="comment"> * 某个组件配置事件时生成一份事件 schema 并存储于 tree，当组件根据 tree 渲染时会</span></span><br><span class="line"><span class="comment"> * 对该事件 schema 进行解析，并从全局 store 获取对应行为，最终根据行为和事件 schema 中配置的行为参数进行组合生成完整的事件方法通过 props 传递给该组件。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> schema：定义数据格式的控制规范，数组结构方便多事件、多行为的扩展。</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line">&#123;</span><br><span class="line">    <span class="attr">actionName</span>: <span class="string">&quot;打开链接&quot;</span>,     <span class="comment">// 行为名称：为组件配置时展示使用</span></span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;fn&quot;</span>,                <span class="comment">// 事件类型：fn 是方法，query 是调用接口等</span></span><br><span class="line">    <span class="attr">params</span>: [<span class="string">&quot;url&quot;</span>],           <span class="comment">// 事件函数入参：表明该行为需要用户配置的参数</span></span><br><span class="line">    <span class="attr">handle</span>: <span class="function">(<span class="params">url</span>) =&gt;</span> &#123;         <span class="comment">// 事件处理函数</span></span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">href</span> = url</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="attr">events</span>: [&#123;  <span class="comment">// 事件 schema：type 事件触发方式、params 用户配置生成的行为参数</span></span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;onClick&quot;</span>,</span><br><span class="line">    <span class="attr">actions</span>: [&#123;</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;fn&quot;</span>,</span><br><span class="line">        <span class="attr">actionName</span>: <span class="string">&quot;打开链接&quot;</span>,</span><br><span class="line">        <span class="attr">params</span>: [&#123; <span class="attr">key</span>: <span class="string">&quot;跳转url&quot;</span>, <span class="attr">value</span>: <span class="string">&quot;www.baidu.com&quot;</span> &#125;]</span><br><span class="line">    &#125;]</span><br><span class="line">&#125;]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 事件 schema 和事件行为将组合成类似如下的事件方法</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    (url)&#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">href</span> = url</span><br><span class="line">    &#125;(<span class="string">&quot;www.baidu.com&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过 props 传递给组件</span></span><br><span class="line">&lt;<span class="title class_">Link</span> onClick=&#123;onClick&#125; /&gt;</span><br></pre></td></tr></table></figure>

  <div align="center">
      <img src="" data-original="/images/web/comfn.png" alt="组件事件配置" width="100%">
  </div>


<h2 id="页面渲染"><a href="#页面渲染" class="headerlink" title="页面渲染"></a>页面渲染</h2><blockquote>
<p>craftjs 内部维护了一颗类似于配置面板存储的 treeJson 组件树但是新增了组件实例，只要将 Root 节点挂载到页面就可以从根节点开始不断递归渲染所有组件。页面切换时通过监听路由变化并将不同 treeJson 传入到 craftjs actions.deserialize。</p>
</blockquote>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// craftjs 内部组件树及其解析：type 值为组件实例</span></span><br><span class="line"><span class="keyword">let</span> treeJson = &#123;</span><br><span class="line">    <span class="attr">root</span>: &#123; <span class="attr">type</span>: <span class="title class_">Root</span>, <span class="attr">parent</span>: <span class="literal">null</span>, <span class="attr">child</span>: [nav,background] &#125;,</span><br><span class="line">    <span class="attr">nav</span>: &#123; <span class="attr">type</span>: <span class="title class_">Nav</span>, <span class="attr">parent</span>: root, <span class="attr">child</span>: [] &#125;,</span><br><span class="line">    <span class="attr">background</span>: &#123; <span class="attr">type</span>: <span class="title class_">Background</span>, <span class="attr">parent</span>: root, <span class="attr">child</span>: [] &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">React</span>.<span class="title function_">createElement</span>(<span class="title class_">Root</span>, props, children)</span><br><span class="line"><span class="attr">children</span>: [ </span><br><span class="line">    <span class="title class_">React</span>.<span class="title function_">createElement</span>(<span class="title class_">Nav</span>, props, children),</span><br><span class="line">    <span class="title class_">React</span>.<span class="title function_">createElement</span>(<span class="title class_">Nav</span>, props, <span class="title class_">Background</span>)</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> craftjs immer 代理实现组件更新：数据变化时重新渲染整棵树并传入新</span></span><br><span class="line"><span class="comment"> * props 更新组件，具体实现可参考以下代码通过在最外层包裹 Context 来传递数据</span></span><br><span class="line"><span class="comment"> * 和更新方法，在数据变化时组件状态会动态更新，但不会像 craftjs 重新渲染。</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;</span><br><span class="line">    <span class="variable constant_">FC</span>,  <span class="comment">// FunctionComponent 函数组件，React.Component 类组件</span></span><br><span class="line">    <span class="title class_">ReactElement</span>,            <span class="comment">// 创建虚拟 DOM</span></span><br><span class="line">    createContext, </span><br><span class="line">    useMemo, </span><br><span class="line">    useState,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; produceWithPatches &#125; <span class="keyword">from</span> <span class="string">&quot;immer&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> initState = &#123;</span><br><span class="line">    <span class="attr">root</span>: &#123; <span class="attr">props</span>: &#123; <span class="attr">num</span>: <span class="string">&quot;red&quot;</span> &#125; &#125;,</span><br><span class="line">    <span class="attr">button</span>: &#123; <span class="attr">props</span>: &#123; <span class="attr">num</span>: <span class="string">&quot;green&quot;</span> &#125; &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">CraftProvider</span> = <span class="title function_">createContext</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Test</span>: <span class="variable constant_">FC</span>&lt;&#123; <span class="attr">children</span>: <span class="title class_">ReactElement</span> &#125;&gt; = <span class="function">(<span class="params">&#123; children &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> [data, setData] = <span class="title function_">useState</span>(initState);</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">dispatch</span> = (<span class="params">fn</span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">const</span> [nextState, patches, inversePatches] = <span class="title function_">produceWithPatches</span>(</span><br><span class="line">            data,</span><br><span class="line">            <span class="function">(<span class="params">draft</span>) =&gt;</span> &#123; <span class="title function_">fn</span>(draft.<span class="property">root</span>.<span class="property">props</span>) &#125;</span><br><span class="line">        );</span><br><span class="line">        <span class="title function_">setData</span>(&#123; ...nextState &#125;);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> actions = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> <span class="title function_">setProps</span> = (<span class="params">fn</span>) =&gt; &#123;</span><br><span class="line">            <span class="title function_">dispatch</span>(fn);</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="keyword">return</span> &#123; setProps &#125;;</span><br><span class="line">    &#125;, [data]);</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">CraftProvider.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">data</span>, <span class="attr">actions</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">CraftProvider.Provider</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Test</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="variable constant_">FC</span>, useContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">CraftProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;./Test&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TestChild</span>: <span class="variable constant_">FC</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> context = <span class="title function_">useContext</span>(<span class="title class_">CraftProvider</span>);</span><br><span class="line">    <span class="keyword">const</span> &#123; data, actions &#125; = context;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;data.root.props.num&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">                actions.setProps((props) =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">                    props.num = Math.random();</span></span><br><span class="line"><span class="language-xml">                &#125;);</span></span><br><span class="line"><span class="language-xml">            &#125;&#125;&gt;change<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">TestChild</span>;</span><br></pre></td></tr></table></figure>

  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@title</span> HTML5 组件拖拽：原始方式有 pc mouse、mobile touch</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 1、设置元素可拖拽属性：&lt;div draggable=&quot;true&quot;&gt;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 2、拖拽元素事件：开始 dragstart、进行 drag、完成 dragend</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> 3、目标元素事件：进入 dragenter、区域内悬停持续 dragover、</span></span><br><span class="line"><span class="comment"> * 离开 dragleave、区域内放置元素 drop、、&lt;div class=&#x27;droptarget&#x27;&gt;</span></span><br><span class="line"><span class="comment">**/</span> </span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;dragstart&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    event.<span class="property">dataTransfer</span>.<span class="title function_">setData</span>(<span class="string">&quot;传递数据&quot;</span>, event.<span class="property">target</span>.<span class="property">id</span>);</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;demo&quot;</span>).<span class="property">innerHTML</span> = <span class="string">&quot;开始拖拽&quot;</span>;    </span><br><span class="line">    event.<span class="property">target</span>.<span class="property">style</span>.<span class="property">opacity</span> = <span class="string">&quot;0.4&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;drag&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;demo&quot;</span>).<span class="property">style</span>.<span class="property">color</span> = <span class="string">&quot;red&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;dragend&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;demo&quot;</span>).<span class="property">innerHTML</span> = <span class="string">&quot;完成拖拽&quot;</span>;</span><br><span class="line">    event.<span class="property">target</span>.<span class="property">style</span>.<span class="property">opacity</span> = <span class="string">&quot;1&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;dragenter&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (event.<span class="property">target</span>.<span class="property">className</span> == <span class="string">&quot;droptarget&quot;</span> ) &#123;</span><br><span class="line">        event.<span class="property">target</span>.<span class="property">style</span>.<span class="property">border</span> = <span class="string">&quot;3px dotted red&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;dragover&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    event.<span class="title function_">preventDefault</span>();        <span class="comment">// 防止拖拽效果被重置</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;dragleave&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (event.<span class="property">target</span>.<span class="property">className</span> == <span class="string">&quot;droptarget&quot;</span>) &#123;</span><br><span class="line">        event.<span class="property">target</span>.<span class="property">style</span>.<span class="property">border</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;drop&quot;</span>, <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    event.<span class="title function_">preventDefault</span>();   <span class="comment">// 浏览器默认阻止元素放置</span></span><br><span class="line">    <span class="keyword">if</span> (event.<span class="property">target</span>.<span class="property">className</span> == <span class="string">&quot;droptarget&quot;</span>) &#123;</span><br><span class="line">        event.<span class="property">target</span>.<span class="property">style</span>.<span class="property">border</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;demo&quot;</span>).<span class="property">style</span>.<span class="property">color</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">var</span> data = event.<span class="property">dataTransfer</span>.<span class="title function_">getData</span>(<span class="string">&quot;Text&quot;</span>);</span><br><span class="line">        event.<span class="property">target</span>.<span class="title function_">appendChild</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(data));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="配置面板"><a href="#配置面板" class="headerlink" title="配置面板"></a>配置面板</h2><blockquote>
<p>出于可复用性考虑首先抽象出一层属性组件用于位置、背景填充、尺寸等属性配置，出于可扩展性考虑而基于属性组件抽象出一层原子组件在输入、多选、单选、滚动条等场景下使用。</p>
</blockquote>
  <div align="center">
      <img src="" data-original="/images/web/com_config.png" alt="相关配置" width="80%">
  </div>


<h2 id="组件化开发"><a href="#组件化开发" class="headerlink" title="组件化开发"></a>组件化开发</h2><blockquote>
<p>为了便于维护而拆分组件并通过低代码脚手架进行独立开发调试和发布。在组件发布后通过资源管理平台对所有组件统一管理并支持选择业务组件构成资产包，平台通过 key 拉取对应资产包渲染出不同的组件物料，真正实现低代码平台开放能力而不再只有平台维护者可以开发，整体架构如上。最终所有组件通过 Monorepo 组件仓库进行统一维护，在低代码搭建平台渲染时通过统一的接口异步拉取所有组件资源进行渲染，加载组件资源方法如下：</p>
</blockquote>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">loadJS</span>(<span class="params">src:string, clearCache=<span class="literal">false</span></span>): <span class="title class_">Promise</span>&lt;<span class="title class_">HTMLScriptElement</span>&gt;&#123;</span><br><span class="line">    <span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>&lt;<span class="title class_">HTMLScriptElement</span>&gt;(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;script&quot;</span>, &#123;&#125;);</span><br><span class="line">        script.<span class="property">type</span> = <span class="string">&quot;text/JavaScript&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span> (clearCache) &#123;</span><br><span class="line">            script.<span class="property">src</span> = src + <span class="string">&quot;?t=&quot;</span> + <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            script.<span class="property">src</span> = src;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (script.<span class="property">addEventListener</span>) &#123;</span><br><span class="line">            script.<span class="title function_">addEventListener</span>(<span class="string">&quot;load&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">resolve</span>(script);</span><br><span class="line">            &#125;);</span><br><span class="line">            script.<span class="title function_">addEventListener</span>(<span class="string">&quot;error&quot;</span>, <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="title function_">reject</span>(e);</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="property">head</span>.<span class="title function_">appendChild</span>(script);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="二、项目难点"><a href="#二、项目难点" class="headerlink" title="二、项目难点"></a>二、项目难点</h1><h2 id="PC-H5-适配"><a href="#PC-H5-适配" class="headerlink" title="PC H5 适配"></a>PC H5 适配</h2><ul>
<li>场景一：<strong>Shopify 类似的建站平台，没有绝对定位组件</strong>，通过规范化 UI 在组件层面提供足够丰富的模版让用户只需在其基础之上进行文案、图片的替换等工作便可实现建站需求。</li>
<li>场景二：<strong>对于营销或官网搭建的场景，组件需要进行绝对定位</strong>，这种 UI 复杂页面有两种方案，一种是类似凡科建站 PC H5 分别使用两套搭建产物，通过在运维层面获取 UserAgent 重定向到相应页面并关联搭建产物，但是用户希望搭建一个 PC 页面就可以自适配出 H5 页面，我们则很难确定一个两端组件对应的位置，最终参考 Wix 平台后确定组件样式可以在 PC H5 独立配置但逻辑部分可通用，并在用户搭建页面时给出另一端默认的样式配置。</li>
<li><strong>实现搭建产物响应式的方案</strong>：基于 rem 在页面渲染时动态计算根元素的字体大小并将 treeJson 中所有 px 单位统一转换为 rem 单位，同时在搭建过程中 PC H5 两端的画布宽度分别以 900 375 作为标准，也就是说该宽度下根元素的字体大小为 100px。</li>
</ul>
<h2 id="表单搭建能力"><a href="#表单搭建能力" class="headerlink" title="表单搭建能力"></a>表单搭建能力</h2><blockquote>
<p>核心是需要实现表单校验、表单联动等复杂操作，整体思路是基于 formilyjs 来实现，其本质就是基于 ViewModel 层定义了一套 json-schema 协议来渲染和控制组件状态，类似于 craftjs 针对于页面编辑场景定义了一套 json-tree，整体架构思想就是通过 craftjs schema-props 控制 formilyjs schema-props，然后去控制具体的表单控件。</p>
</blockquote>
  <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// json-schema</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;object&quot;</span>,</span><br><span class="line">    <span class="attr">properties</span>: &#123;</span><br><span class="line">        <span class="attr">name</span>: &#123;</span><br><span class="line">            <span class="attr">title</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;x-component&quot;</span>: <span class="string">&quot;Input&quot;</span>,</span><br><span class="line">            <span class="string">&quot;x-decorator&quot;</span>: <span class="string">&quot;Field&quot;</span>,</span><br><span class="line">            <span class="string">&quot;x-component-props&quot;</span>: &#123;&#125;,</span><br><span class="line">            <span class="string">&quot;x-validator&quot;</span>: &#123; <span class="attr">required</span>: <span class="literal">true</span> &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">age</span>: &#123;</span><br><span class="line">            <span class="attr">title</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;x-component&quot;</span>: <span class="string">&quot;InputNumber&quot;</span>,</span><br><span class="line">            <span class="string">&quot;x-decorator&quot;</span>: <span class="string">&quot;Field&quot;</span>,</span><br><span class="line">            <span class="string">&quot;x-component-props&quot;</span>: &#123;&#125;,</span><br><span class="line">            <span class="string">&quot;x-validator&quot;</span>: &#123; <span class="attr">required</span>: <span class="literal">true</span>, <span class="attr">min</span>: <span class="number">0</span>, <span class="attr">max</span>: <span class="number">50</span> &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

  <div align="center">
      <img src="" data-original="/images/web/formData.png" alt="相关配置" width="100%">
  </div>


<h1 id="三、主流搭建平台"><a href="#三、主流搭建平台" class="headerlink" title="三、主流搭建平台"></a>三、主流搭建平台</h1><blockquote>
<p>低代码搭建平台的核心不在于功能复杂、能力强大，而是在垂直领域是否深入、交互是否友好，有自己的特点，比如可快速生成任意低代码平台的阿里 lowcode engine、拥有可靠建站能力的 shopify、拥有最强大布局能力的 webflow、拥有完整小程序搭建能力的 ZION。</p>
</blockquote>
<ul>
<li><strong>阿里低代码引擎 Low-Code Engine</strong>：一个专注于生产低代码平台的引擎，核心提供了组件开发脚手架、定义了物料接入规范、造物平台可管理物料、配置化生成低代码组件，出码能力而可以二次编辑，但是交互复杂和上手成本大。适用于公司内部中后台系统，供技术人员使用。</li>
<li><strong>轻流、码匠</strong>：针对中后台领域。轻流是一个可商用的针对企业报销、CRM、设备管理等无代码开发平台，交互友好并且上手成本低，可通过图形化方式实现逻辑编排。码匠底层基于国外开源框架 appsmith 具有优秀的拖拽能力，搭建平台和数据库打通，可以快速实现全栈应用。</li>
<li><strong>shopify、wix</strong>：针对建站领域。shopify 提供丰富模版而可以快速搭建，拥有友好的 SEO、商品管理等能力，是外贸企业建站的首选。wix 则组件布局能力更强，对设计稿还原度更高。</li>
<li><strong>webflow</strong>：偏向于官网搭建领域，有强大的布局和定位能力，可实现复杂的 UI 和交互效果。</li>
<li><strong>微软的低代码平台</strong>：使用时如同 word 具有复杂且全面的功能，但对新手很不友好。</li>
<li><strong>网易数帆、腾讯微搭</strong>：都是做建站方向，虽然功能齐全，但交互不友好，没有出彩点。</li>
<li><strong>ZION</strong>：专注于小程序领域的低代码搭建平台，可以实现一个完整小程序应用的搭建，有自己的能力特色和护城河，画布大小可以无效缩放，适用营销、留资、客户运营等场景。</li>
</ul>

    </div>

    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;margin-top:20px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
    
        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>chuangges
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="https://chuangges.gitee.io/2023/08/14/22-%E4%BD%8E%E4%BB%A3%E7%A0%81%E5%B9%B3%E5%8F%B0/" title="低代码平台开发">https://chuangges.gitee.io/2023/08/14/22-低代码平台/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/zh_CN" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>


      <footer class="post-footer">

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2023/03/02/21-vue3%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/" rel="prev" title="Vue3 项目开发">
      <i class="fa fa-chevron-left"></i> Vue3 项目开发
    </a></div>
      <div class="post-nav-item">
    <a href="/2023/09/04/23-next%E9%A6%96%E5%B1%8F%E6%B8%B2%E6%9F%93/" rel="next" title="最强首屏渲染方案 NextJS">
      最强首屏渲染方案 NextJS <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%80%E3%80%81%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88"><span class="nav-number">1.</span> <span class="nav-text">一、技术方案</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84"><span class="nav-number">1.1.</span> <span class="nav-text">整体架构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BB%84%E4%BB%B6%E5%B8%83%E5%B1%80"><span class="nav-number">1.2.</span> <span class="nav-text">组件布局</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BB%84%E4%BB%B6%E4%BA%8B%E4%BB%B6"><span class="nav-number">1.3.</span> <span class="nav-text">组件事件</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%A1%B5%E9%9D%A2%E6%B8%B2%E6%9F%93"><span class="nav-number">1.4.</span> <span class="nav-text">页面渲染</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%85%8D%E7%BD%AE%E9%9D%A2%E6%9D%BF"><span class="nav-number">1.5.</span> <span class="nav-text">配置面板</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BB%84%E4%BB%B6%E5%8C%96%E5%BC%80%E5%8F%91"><span class="nav-number">1.6.</span> <span class="nav-text">组件化开发</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%BA%8C%E3%80%81%E9%A1%B9%E7%9B%AE%E9%9A%BE%E7%82%B9"><span class="nav-number">2.</span> <span class="nav-text">二、项目难点</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#PC-H5-%E9%80%82%E9%85%8D"><span class="nav-number">2.1.</span> <span class="nav-text">PC H5 适配</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%A1%A8%E5%8D%95%E6%90%AD%E5%BB%BA%E8%83%BD%E5%8A%9B"><span class="nav-number">2.2.</span> <span class="nav-text">表单搭建能力</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%89%E3%80%81%E4%B8%BB%E6%B5%81%E6%90%AD%E5%BB%BA%E5%B9%B3%E5%8F%B0"><span class="nav-number">3.</span> <span class="nav-text">三、主流搭建平台</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="chuangges" src="" data-original="/images/chuang.png">
  <p class="site-author-name" itemprop="name">chuangges</p>
  <div class="site-description" itemprop="description">chuangge's blog.</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">39</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">7</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/chuangges" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;chuangges" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://stackoverflow.com/yourname" title="StackOverflow → https:&#x2F;&#x2F;stackoverflow.com&#x2F;yourname" rel="external nofollow noopener noreferrer" target="_blank"><i class="fab fa-stack-overflow fa-fw"></i>StackOverflow</a>
      </span>
  </div>


  <div class="links-of-blogroll motion-element">
    <div class="links-of-blogroll-title"><i class="fa fa-link fa-fw"></i>
      友情链接
    </div>
    <ul class="links-of-blogroll-list">
        <li class="links-of-blogroll-item">
          <a href="https://juejin.im/" title="https:&#x2F;&#x2F;juejin.im&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">掘金</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://www.nowcoder.com/" title="https:&#x2F;&#x2F;www.nowcoder.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">牛客网</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://www.docschina.org/" title="https:&#x2F;&#x2F;www.docschina.org&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">印记中文</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="http://tool.lu/" title="http:&#x2F;&#x2F;tool.lu&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">在线工具</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://tinypng.com/" title="https:&#x2F;&#x2F;tinypng.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">图片压缩</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://bigjpg.com/" title="https:&#x2F;&#x2F;bigjpg.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">图片放大</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://tool.chinaz.com/" title="https:&#x2F;&#x2F;tool.chinaz.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">站长工具</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://learn-anything.xyz/" title="https:&#x2F;&#x2F;learn-anything.xyz&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">Learn-Anything</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://stackoverflow.com/" title="https:&#x2F;&#x2F;stackoverflow.com&#x2F;" rel="external nofollow noopener noreferrer" target="_blank">Stack-Overflow</a>
        </li>
    </ul>
  </div>

      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2024</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">chuangge</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-chart-area"></i>
    </span>
    <span title="站点总字数">1m</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">15:14</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="external nofollow noopener noreferrer" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="external nofollow noopener noreferrer" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","model":{"jsonPath":"/live2dw/assets/tororo.model.json"},"display":{"position":"left","width":180,"height":360},"mobile":{"show":false},"react":{"opacityDefault":1,"opacityOnHover":1},"log":false,"tagMode":false});</script>
        <style>
            [bg-lazy] {
                background-image: none !important;
                background-color: #eee !important;
            }
        </style>
        <script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                preloadRatio: 1,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(e.href.match(t)||e.href.match(r))&&(e.href=a.dataset.original)})});</script><script>!function(e){e.imageLazyLoadSetting.processImages=t;var n=e.imageLazyLoadSetting.isSPA,i=e.imageLazyLoadSetting.preloadRatio||1,r=o();function o(){var t=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")),e=Array.prototype.slice.call(document.querySelectorAll("[bg-lazy]"));return t.concat(e)}function t(){n&&(r=o());for(var t,a=0;a<r.length;a++)0<=(t=(t=r[a]).getBoundingClientRect()).bottom&&0<=t.left&&t.top<=(e.innerHeight*i||document.documentElement.clientHeight*i)&&function(){var t,e,n,i,o=r[a];e=function(){r=r.filter(function(t){return o!==t})},(t=o).hasAttribute("bg-lazy")?(t.removeAttribute("bg-lazy"),e&&e()):(n=new Image,i=t.getAttribute("data-original"),n.onload=function(){t.src=i,t.removeAttribute("data-original"),e&&e()},t.src!==i&&(n.src=i))}()}function a(){clearTimeout(t.tId),t.tId=setTimeout(t,500)}t(),document.addEventListener("scroll",a),e.addEventListener("resize",a),e.addEventListener("orientationchange",a)}(this);</script></body>
</html>
